#pragma once
#include<sstream>
#include"HttpContex.hpp"
#include"Server.hpp"

class HttpServer{
    private:
        using Handle = std::function<void(const HttpRequest &,HttpResponse *)>;
        using HandleMap = std::vector<std::pair<std::regex, Handle>>;
        HandleMap _get_route;//GET请求路由表 
        HandleMap _post_route;//POST请求路由表
        HandleMap _put_route;//PUT请求路由表  
        HandleMap _delete_route;//DELETE请求路由表
        std::string _base_dir;//静态资源根目录
        TcpServer _server;
    private:
        bool IsStaticResource(const HttpRequest &req) //判断是否是静态资源请求
        {
            //静态资源请求必须设置了静态资源根目录 / 请求方法必须是GET或HEAD / 请求路径必须是合法路径
            if(_base_dir.empty() == true || (req._method != "GET" && req._method != "HEAD") || (Util::ValidPath(req._path) == false)){
                return false;
            }
            //  请求的资源必须存在,且是⼀个普通⽂件
            //有⼀种请求⽐较特殊 -- ⽬录：/, /image/， 这种情况给后边默认追加⼀个index.html
            // index.html    /image/a.png
            // 不要忘了前缀的相对根⽬录,也就是将请求路径转换为实际存在的路径/image/a.png  ->   ./wwwroot/image/a.png
            std::string req_path = _base_dir + req._path;//临时变量判断
            if(req_path.back() == '/'){
                req_path += "index.html";
            }
            if(Util::IsRegularFile(req_path) == false){
                return false;
            }
            return true;
        }
        void StaticResourceHandle(const HttpRequest &req,HttpResponse *resp)//静态资源请求的处理,将静态资源⽂件的数据读取出来，放到rsp的_body中, 并设置mime
        {
            std::string path = _base_dir + req._path;//临时变量判断
            if(path.back() == '/'){
                path += "index.html";
            }
            bool ret = Util::ReadFile(path,&resp->_body);//把读取的数据放入到resp->_body中
            if(ret){
                std::string mim = Util::ExtMime(path);
                resp->SetHeader("Content-Type",mim);
            }
        }
        void Dispatch(HttpRequest &req,HttpResponse *resp, HandleMap &handles)//功能性请求的分类处理
        {
            //在对应请求⽅法的路由表中，查找是否含有对应资源请求的处理函数，有则调⽤，没有则404
            //思想：路由表存储的时键值对 -- 正则表达式 & 处理函数
            //使⽤正则表达式，对请求的资源路径进⾏正则匹配，匹配成功就使⽤对应函数进⾏处理
            //  /numbers/(\d+)       /numbers/12345
            for(auto handle_pair : handles){
                std::regex &regex = handle_pair.first;
                Handle &func = handle_pair.second;
                bool ret = std::regex_match(req._path,req._matches,regex);//匹配得到的各个字段放入到req._matches中
                if(!ret){
                    continue;
                }else{
                    func(req,resp);
                    return;
                }
            }
            resp->_status = 404;

        }
        void Route(HttpRequest &req , HttpResponse *resp)
        {
            //1. 对请求进⾏分辨，判断是⼀个静态资源请求，还是⼀个功能性请求
            // 功能性请求，则需要通过⼏个请求路由表来确定是否有处理函数
            // 既不是静态资源请求，也没有设置对应的功能性请求处理函数，就返回405
            if(IsStaticResource(req)){
                StaticResourceHandle(req,resp);//是⼀个静态资源请求, 则进⾏静态资源请求的处理
                return;
            }
            if(req._method == "GET" || req._method == "HEAD"){
                Dispatch(req,resp, _get_route);
            }else if(req._method == "POST"){
                Dispatch(req,resp, _post_route);
            }else if(req._method == "PUT"){
                Dispatch(req,resp, _put_route);
            }else if(req._method == "DELETE"){
                Dispatch(req,resp, _delete_route);
            }else{
                //不是静态资源请求，也不是功能性请求，则返回405
                resp->_status = 405;// Method Not Allowed
            }
            return;
        }
        void ErrorHandle(const HttpRequest &req , HttpResponse *resp)//错误处理，填充⼀个错误显⽰⻚⾯数据到rsp中
        {
            std::string html;
            // 构建简单的HTML错误页面
            html += "<!DOCTYPE html>\n";
            html += "<html>\n";
            html += "<head>\n";
            html += "<title>Error " + std::to_string(resp->_status) + "</title>\n";
            html += "</head>\n";
            html += "<body>\n";
            html += "<h1>Error " + std::to_string(resp->_status) + "</h1>\n";
            html += "<p>" + Util::StatusDescription(resp->_status) + "</p>\n";
            html += "<p><a href='/'>Go back to home page</a></p>\n";
            html += "</body>\n";
            html += "</html>";
            // 将⻚⾯数据，当作响应正⽂，放⼊rsp中
            resp->SetContent(html);
        }
        void SetConnected(const ConnPtr &conn) //设置协议上下文
        {
            conn->SetContext(HttpContext());
            LOG_DBG("New Connection %ld",conn->ConnId());
        }
         void SetAndSendHttpResponse(const ConnPtr &conn,const HttpRequest &req,HttpResponse &resp)//将HttpResponse中的要素按照http协议格式进⾏组织，发送
        {
            //1. 先完善头部字段
            if(req.IsKeepAlive() == true){
                resp.SetHeader("Connection","keep-alive");
            }else{
                resp.SetHeader("Connection","close");
            }
            if(resp._body.empty() == false){
                if(resp.HasHeader("Content-Length") == false)
                    resp.SetHeader("Content-Length",std::to_string(resp._body.size()));
                if(resp.HasHeader("Content-Type") == false)
                    resp.SetHeader("Content-Type","application/octet-stream");//默认是二进制数据
            }
            if(resp._redir_flag == true){
                resp.SetHeader("Location",resp._redir_url);
            }
            //2.将resp中的要素,按照http协议格式进⾏组织,并发送给客户端
            std::stringstream resp_str;
            //2.1构建响应请求行
            resp_str << "HTTP/1.1 " << resp._status << " " << Util::StatusDescription(resp._status) << "\r\n";
            //2.2构建响应头部
            for(auto &header : resp._headers){
                resp_str << header.first << ": " << header.second << "\r\n";
            }
            //2.3构建空行
            resp_str << "\r\n";
            //2.4构建响应正⽂
            resp_str << resp._body;
            //3.发送数据
            conn->Send(resp_str.str().c_str(),resp_str.str().size());
        }
        void SendMessage(const ConnPtr &conn,Buffer *buf)  //缓冲区数据解析 + 处理请求
        {
            while(buf->ReadableSize() > 0)
            {
                //1.获取上下文
                HttpContext *context = conn->GetContext()->GetData<HttpContext>();
                
                //2.通过上下文,对缓冲区数据进行解析,得到httprequest
                context->RecvAndPraseRequest(buf);
                HttpRequest &request = context->Request();
                HttpResponse response(context->ResponseStatus());// 构造响应对象
                //  2.1 如果缓冲区的数据解析出错，就直接回复出错响应
                //  2.2 如果解析正常，且请求已经获取完毕，才开始去进⾏处理
                if(context->ResponseStatus() >= 400){// 如果缓冲区的数据解析出错，就直接回复出错响应
                    //进⾏错误响应，关闭连接
                    ErrorHandle(request, &response);//填充⼀个错误显⽰⻚⾯数据到rsp中
                    SetAndSendHttpResponse(conn,request,response);//组织响应发送给客⼾端
                    context->ReSet();//重置上下文
                    buf->Clear();//出错就清空缓冲区
                    conn->CheckAndShutdown();//关闭连接   
                    return;
                }else if(context->RecvStatus() != HttpRecvStatus::RECV_HTTP_OVER){
                    //当前请求还没有接收完整,则退出，等新数据到来再重新继续处理
                    return;
                }   
                
                //3根据httprequest的method,选择对应的路由表,进行处理
                Route(request, &response);
                //4.将处理结果构成httpresponse
                SetAndSendHttpResponse(conn,request,response);
                //5.重置上下文
                context->ReSet();
                //6.根据长短连接判断,是关闭连接,还是继续处理
                if(!response.IsKeepAlive()){
                    conn->CheckAndShutdown();
                }
            }
            
        }
    public:
        HttpServer(int port , int timeout = 3000)
            :_server(port)
        {
            //设置连接的上下文和处理发送请求
            _server.SetMessageCallback(std::bind(&HttpServer::SendMessage,this,std::placeholders::_1,std::placeholders::_2));
            _server.SetConnectedCallback(std::bind(&HttpServer::SetConnected,this,std::placeholders::_1));
            _server.EnableInactiveConnectionClose(timeout);//设置超时时间

        }
        void SetBaseDir(const std::string& base_dir)
        {
            assert(Util::IsDirectory(base_dir));
            _base_dir = base_dir;
        }
        void SetGET(const std::string pattern ,const Handle &handle)
        {
            _get_route.push_back(std::make_pair(std::regex(pattern),handle));
        }
        void SetPOST(const std::string pattern ,const Handle &handle)
        {
            _post_route.push_back(std::make_pair(std::regex(pattern),handle));
        }
        void SetPUT(const std::string pattern ,const Handle &handle)
        {
            _put_route.push_back(std::make_pair(std::regex(pattern),handle));
        }
        void SetDELETE(const std::string pattern ,const Handle &handle)
        {
            _delete_route.push_back(std::make_pair(std::regex(pattern),handle));
        }
        void SetThreadNum(int num)
        {
            _server.SetThreadNum(num);
        }
        void Start()
        {
            _server.Start();
        }

};






